/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugins.assembly.utils;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;

/**
 *
 */
public final class ProjectUtils {

    private ProjectUtils() {}

    public static String getClassifier(Artifact artifact) {
        String classifier = artifact.getClassifier();
        if (classifier != null && classifier.length() == 0) {
            classifier = null;
        }
        return classifier;
    }

    public static Set<MavenProject> getProjectModules(
            final MavenProject project,
            final List<MavenProject> reactorProjects,
            final boolean includeSubModules,
            final Logger logger)
            throws IOException {
        final Set<MavenProject> singleParentSet = Collections.singleton(project);

        final Set<MavenProject> moduleCandidates = new LinkedHashSet<>(reactorProjects);

        final Set<MavenProject> modules = new LinkedHashSet<>();

        // we temporarily add the master project to the modules set, since this
        // set is pulling double duty as a set of
        // potential module parents in the tree rooted at the master
        // project...this allows us to use the same looping
        // algorithm below to discover both direct modules of the master project
        // AND modules of those direct modules.
        modules.add(project);

        int changed;

        do {
            changed = 0;

            for (final Iterator<MavenProject> candidateIterator = moduleCandidates.iterator();
                    candidateIterator.hasNext(); ) {
                final MavenProject moduleCandidate = candidateIterator.next();

                if (moduleCandidate.getFile() == null) {
                    logger.warn("Cannot compute whether " + moduleCandidate.getId() + " is a module of: "
                            + project.getId() + "; it does not have an associated POM file on the local filesystem.");
                    continue;
                }

                Set<MavenProject> currentPotentialParents;
                if (includeSubModules) {
                    currentPotentialParents = new LinkedHashSet<>(modules);
                } else {
                    currentPotentialParents = singleParentSet;
                }

                for (final MavenProject potentialParent : currentPotentialParents) {
                    if (potentialParent.getFile() == null) {
                        logger.warn("Cannot use: " + moduleCandidate.getId()
                                + " as a potential module-parent while computing the module set for: "
                                + project.getId()
                                + "; it does not have an associated POM file on the local filesystem.");
                        continue;
                    }

                    // if this parent has an entry for the module candidate in
                    // the path adjustments map, it's a direct
                    // module of that parent.
                    if (projectContainsModule(potentialParent, moduleCandidate)) {
                        // add the candidate to the list of modules (and
                        // potential parents)
                        modules.add(moduleCandidate);

                        // remove the candidate from the candidate pool, because
                        // it's been verified.
                        candidateIterator.remove();

                        // increment the change counter, to show that we
                        // verified a new module on this pass.
                        changed++;

                        // We need to move on to the next candidate since this one was just verified
                        break;
                    }
                }
            }
        } while (changed != 0);

        // remove the master project from the modules set, now that we're done
        // using it as a set of potential module
        // parents...
        modules.remove(project);

        return modules;
    }

    private static boolean projectContainsModule(final MavenProject mainProject, final MavenProject moduleProject)
            throws IOException {
        final List<String> modules = mainProject.getModules();
        final File basedir = mainProject.getBasedir();

        final File moduleFile = moduleProject.getFile().getCanonicalFile();

        File moduleBasedir = moduleProject.getBasedir();

        if (moduleBasedir == null) {
            moduleBasedir = moduleFile.getParentFile();

            if (moduleBasedir == null) {
                moduleBasedir = new File(".");
            }
        }

        moduleBasedir = moduleBasedir.getCanonicalFile();

        for (final String moduleSubpath : modules) {
            final File moduleDir = new File(basedir, moduleSubpath).getCanonicalFile();

            if (moduleDir.equals(moduleFile) || moduleDir.equals(moduleBasedir)) {
                return true;
            }
        }

        return false;
    }
}
